package com.jmebananas.renderer.pass;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Iterator;

import com.jme.input.MouseInput;
import com.jme.input.MouseInputListener;
import com.jme.renderer.Renderer;
import com.jme.scene.Line;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.state.AlphaState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.util.geom.BufferUtils;
import com.jmebananas.game.tile.Tile;
import com.jmebananas.game.tile.TileListener;
import com.jmebananas.util.UniqueName;

/**
 * HUDRenderPass
 * 
 * @author Glenn Powell
 * 
 */
public class TileRenderPass extends AdvancedRenderPass implements MouseInputListener {

	private static final long serialVersionUID = 1L;
	

	private ArrayList<TileListener> listeners = new ArrayList<TileListener>();

	private Node rootNode;
	
	private Tile currentFocusTile = null;
	
	private Tile currentHoverTile = null;
	
	private boolean mouseButtonPressed = false;

	
	private boolean pickAlphaMask = true;
	
	private boolean stickyMouse = true;

	
	private Node debugNode;
	
	private Line debugLine;

	private boolean renderBounds = false;


	public TileRenderPass() {
		this(DEFAULT_RENDERPASS_INDEX);
	}

	public TileRenderPass(int index) {
		super(index);

		rootNode = new Node(UniqueName.createName("root"));
		super.add(rootNode);

		ZBufferState zBufferState = DisplaySystem.getDisplaySystem().getRenderer().createZBufferState();
		zBufferState.setEnabled(true);
		zBufferState.setFunction(ZBufferState.CF_LEQUAL);
		rootNode.setRenderState(zBufferState);

		rootNode.setCullMode(Node.CULL_NEVER);
		rootNode.setRenderQueueMode(com.jme.renderer.Renderer.QUEUE_ORTHO);

		rootNode.getLocalScale().y = -1;
		rootNode.getLocalTranslation().y = DisplaySystem.getDisplaySystem().getHeight();

		AlphaState alphaState = DisplaySystem.getDisplaySystem().getRenderer().createAlphaState();
		alphaState.setBlendEnabled(true);
		alphaState.setSrcFunction(AlphaState.SB_SRC_ALPHA);
		alphaState.setDstFunction(AlphaState.DB_ONE_MINUS_SRC_ALPHA);
		alphaState.setTestEnabled(true);
		alphaState.setEnabled(true);
		alphaState.setTestFunction(AlphaState.TF_GREATER);
		rootNode.setRenderState(alphaState);

		MouseInput.get().addListener(this);
		
		debugNode = new Node(UniqueName.createName("debug"));
		debugNode.setIsCollidable(false);
		debugNode.setRenderQueueMode(Renderer.QUEUE_SKIP);
		super.add(debugNode);

		zBufferState = DisplaySystem.getDisplaySystem().getRenderer().createZBufferState();
		zBufferState.setEnabled(false);
		debugNode.setRenderState(zBufferState);
	
		FloatBuffer vertBuf = BufferUtils.createVector3Buffer(1);
		vertBuf.put(0).put(0).put(0);
		debugLine = new Line(debugNode.getName() + "_line", vertBuf, null, null, null);
		debugLine.setMode(Line.LOOP);
		debugNode.attachChild(debugLine);
	}

	public void add(Spatial spatial) {
		rootNode.attachChild(spatial);
	}

	public void addTileListener(TileListener listener) {
		listeners.add(listener);
	}

	public TileListener[] getTileListeners() {
		return listeners.toArray(new TileListener[listeners.size()]);
	}

	public void removeTileListener(TileListener listener) {
		listeners.remove(listener);
	}
	
	public boolean isPickAlphaMask() {
		return pickAlphaMask;
	}
	
	public void setPickAlphaMask(boolean pickAlphaMask) {
		this.pickAlphaMask = pickAlphaMask;
	}
	
	public boolean isStickyMouse() {
		return stickyMouse;
	}
	
	public void setStickyMouse(boolean stickyMouseButtons) {
		this.stickyMouse = stickyMouseButtons;
	}

	public boolean isRenderBounds() {
		return renderBounds;
	}

	public void setRenderBounds(boolean renderBounds) {
		this.renderBounds = renderBounds;
	}

	public void doRender(Renderer r) {
		super.doRender(r);

		if (renderBounds) {
			r.setOrtho();
			AffineTransform worldTileTransform = new AffineTransform();
			Rectangle tempRect = new Rectangle();
			for (Iterator<Spatial> itr = rootNode.getChildren().iterator(); itr.hasNext();) {
				Spatial spatial = itr.next();
				if (spatial.isCollidable()) {
					if (spatial instanceof Tile) {
						Tile childTile = (Tile) spatial;
						worldTileTransform.setToTranslation(childTile.getX(), childTile.getY());
						worldTileTransform.rotate(childTile.getAngle());
						worldTileTransform.scale(childTile.getWorldScale().x, childTile.getWorldScale().y);
						tempRect.setBounds(0, 0, (int) childTile.getWidth(), (int) childTile.getHeight());
						Shape worldTileBound = worldTileTransform.createTransformedShape(tempRect);
						
						ArrayList<Point2D> points = new ArrayList<Point2D>();
						float[] coords = new float[6];
						for (PathIterator pathItr = worldTileBound.getPathIterator(null); !pathItr.isDone(); pathItr.next()) {
							int seg = pathItr.currentSegment(coords);
							if (seg == PathIterator.SEG_LINETO) {
								points.add(new Point2D.Float(coords[0], coords[1]));
							}
						}
						FloatBuffer vertBuf = BufferUtils.createVector3Buffer(points.size() + 1);
						for (Iterator<Point2D> pointItr = points.iterator(); pointItr.hasNext();) {
							Point2D point = pointItr.next();
							vertBuf.put((float) point.getX()).put((float) point.getY()).put(0);
						}
						vertBuf.put((float) points.get(0).getX()).put((float) points.get(0).getY()).put(0);
						debugLine.setDefaultColor(childTile.getColor());
						debugLine.reconstruct(vertBuf, null, null, null);
						debugLine.draw(r);
					}
				}
			}
			r.unsetOrtho();
		}
	}
	
	private Tile pickTile(Point point) {
		int localX = 0;
		int localY = 0;
		Tile tile = null;
		Point localPoint = new Point();
		for (Iterator<Spatial> itr = rootNode.getChildren().iterator(); itr.hasNext();) {
			Spatial spatial = itr.next();
			if (spatial.isCollidable()) {
				if (spatial instanceof Tile) {
					Tile childTile = (Tile) spatial;
					if (childTile.pickTile(point, localPoint, pickAlphaMask)) {
						if (tile == null || tile.getLocalTranslation().z <= childTile.getLocalTranslation().z) {
							localX = localPoint.x;
							localY = localPoint.y;
							tile = childTile;
						}
					}
				}
			}
		}
		point.setLocation(localX, localY);
		return tile;
	}

	public void onButton(int button, boolean pressed, int x, int y) {
		Point point = new Point(x, y);
		Tile tile = null;
		if (!pressed && stickyMouse && currentFocusTile != null) {
			tile = currentFocusTile;
			tile.pickTile(point, point, false);
		} else {
			tile = pickTile(point);
			if (pressed)
				currentFocusTile = tile;
		}
		if (tile != null) {
			tile.onButton(button, pressed, point.x, point.y);
			TileListener[] listeners = getTileListeners();
			for (int i = 0; i < listeners.length; ++i)
				listeners[i].tileMouseButton(tile, button, pressed, point.x, point.y);
		}
		mouseButtonPressed = pressed;
	}

	public void onMove(int deltaX, int deltaY, int newX, int newY) {
		Point point = new Point(newX, newY);
		Tile tile = null;
		if (mouseButtonPressed && stickyMouse && currentHoverTile != null) {
			tile = currentHoverTile;
			tile.pickTile(point, point, false);
		} else {
			tile = pickTile(point);
		}
		if (tile != currentHoverTile) {
			if (currentHoverTile != null)
				currentHoverTile.onExit();
			if (tile != null)
				tile.onEnter(point.x, point.y);
			currentHoverTile = tile;
		}
		if (tile != null) {
			tile.onMove(point.x, point.y);
			TileListener[] listeners = getTileListeners();
			for (int i = 0; i < listeners.length; ++i)
				listeners[i].tileMouseMove(tile, point.x, point.y);
		}
	}

	public void onWheel(int wheelDelta, int x, int y) {
		Point point = new Point(x, y);
		Tile tile = pickTile(point);
		if (tile != null) {
			tile.onWheel(wheelDelta, point.x, point.y);
			TileListener[] listeners = getTileListeners();
			for (int i = 0; i < listeners.length; ++i)
				listeners[i].tileMouseWheel(tile, wheelDelta, point.x, point.y);
		}
	}

}
